namespace cxxcall
{

static void
cb_receiver (void)
{
#ifndef __clang__
  register cb_fct *env __asm__ ("%ecx");
#else
  cb_fct *env;
  __asm__ ("movl %%ecx, %0" : "+r" (env));
#endif
  cb_data data;

  __asm__ __volatile__ ("movl %%ebx, %0" : "=m" (data.structaddr));
  data.flags = 0;
  data.curr = (uintptr_t)__builtin_frame_address (0) + 2 * WSIZE;
  data.rtype = type::void_t;
  env->fn (&data, env->arg);

  // Placeholders for %eax and %edx.
  long ret_1 = 0, ret_2 = 0;

  switch (data.rtype)
    {
      case type::void_t:
        break;

#define SET_REG(Name, Value)   \
  __asm__ __volatile__ ("mov %0, %" Name "\n\t" : : "m" (Value))

#define RET(Typecode, Member)   \
  case type::Typecode:   \
    ret_1 = data.tmp.Member;   \
    break

      RET (schar_t, sch);
      RET (uchar_t, uch);
      RET (sshort_t, shrt);
      RET (ushort_t, ushrt);
      RET (sint_t, ival);
      RET (uint_t, uival);
      RET (slong_t, lval);
      RET (ulong_t, ulval);

      case type::sllong_t:
      case type::ullong_t:
        ret_1 = data.tmp.parts.i.u[0];
        ret_2 = data.tmp.parts.i.u[1];
        break;
      case type::float_t:
        __asm__ __volatile__ ("flds %0" : : "m" (data.tmp.flt));
        break;
      case type::double_t:
        __asm__ __volatile__ ("fldl %0" : : "m" (data.tmp.dbl));
        break;
      case type::ldouble_t:
        __asm__ __volatile__ ("fldt %0" : : "m" (data.tmp.ldbl));
        break;

      case type::struct_t:
        if (data.flags & av_base::reg_struct_ret)
          switch (data.rsize)
            {
              case 1:
                ret_1 = *(uint8_t *)data.raddr;
                break;
              case 2:
                ret_1 = *(uint16_t *)data.raddr;
                break;
              case 4:
                ret_1 = *(uint32_t *)data.raddr;
                break;
              default:
                ret_1 = ((uint32_t *)data.raddr)[0];
                ret_2 = ((uint32_t *)data.raddr)[1];
                break;
            }
        else
          {
            if (!(data.flags & av_base::msvc_struct_ret))
              {
                __asm__ __volatile__
                  (
                    "movl %0, %%eax\n\t"
                    "leal -8(%%ebp), %%esp\n\t"
                    "popl %%edi\n\t"
                    "popl %%esi\n\t"
                    "leave\n\t"
                    "ret $4"
                    : : "g" (data.raddr)
                  );

                __builtin_unreachable ();
              }

            ret_1 = (uintptr_t)data.raddr;
          }
    }

  if (data.flags & av_base::stdcall_fn)
    {
      __asm__ __volatile__
        (
          "movl %2, %%eax\n\t"
          "movl %3, %%edx\n\t"
          "movl %0, %%ecx\n\t"
          "movl %1, %%esp\n\t"
          "jmpl *%%ecx"
          : : "g" (*(void **)__builtin_frame_address (0)), "g" (data.curr),
              "g" (ret_1), "g" (ret_2)
        );

      __builtin_unreachable ();
    }

  SET_REG ("%eax", ret_1);
  SET_REG ("%edx", ret_2);
}

static const size_t TRAMP_LEN = 12;

void cb_data::fill (void *outp, void (*fn) (cb_data *, void *), void *argx)
{
  void *fct = (void *)cb_receiver;
  void **arg = (void **)((char *)outp + TRAMP_LEN);

  ((unsigned char *)outp)[0] = 0xb9;
  memcpy ((char *)outp + 1, &arg, sizeof (arg));
  ((unsigned char *)outp)[5] = 0xe9;

  intptr_t tmp = (intptr_t)fct - (intptr_t)outp - 10;
  memcpy ((char *)outp + 6, &tmp, sizeof (tmp));
  uint16_t pad = 0x9090;
  memcpy ((char *)outp + 10, &pad, sizeof (pad));

  arg[0] = (void *)fn, arg[1] = argx;
}

} // namespace cxxcall
